package com.upb.webserver.rest.fisco;

import cn.hutool.json.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.upb.webserver.common.constant.RestConstant;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.base.BasePageResponse;
import com.upb.webserver.common.pojo.base.BaseResponse;
import com.upb.webserver.common.pojo.order.ReqAddNewNode2ChainVO;
import com.upb.webserver.common.pojo.order.RespAddNodeVO;
import com.upb.webserver.common.pojo.rest.fisco.chainmgr.req.*;
import com.upb.webserver.common.pojo.rest.fisco.chainmgr.resp.*;
import com.upb.webserver.common.pojo.rest.fisco.data.resp.RspContractDeployDTO;
import com.upb.webserver.common.pojo.rest.fisco.data.resp.UserInfo;
import com.upb.webserver.common.properties.RestProperties;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.WebServerTools;
import com.upb.webserver.rest.AbstractRestService;
import com.upb.webserver.rest.RespInitHost;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.math.BigInteger;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.upb.webserver.common.constant.RestConstant.URI_WEBASE_CHAIN_IMAGE_TYPE_LIST;

/**
 * webase-chain-manager interface service
 *
 * @related: fisco/chain/FiscoChainService
 */
@Slf4j
@Service
public class FiscoChainMgrRestService extends AbstractRestService {

    @Autowired
    private RestProperties restProperties;
    private String serverName = RestConstant.SERVER_NAME_WEBASE_CHAIN_MANAGER;

    @PostConstruct
    public void init() {
        initServerAddressMap(serverName, restProperties.getWebaseChainManagerAddressList());
    }


    /**
     * @param param
     * @return
     */
    public BaseResponse addRunningFiscoChain(ReqAddRunningFiscoChainInfo param) {
        return httpPost(RestConstant.URI_WEBASE_CHAIN_NEW, param, BaseResponse.class);
    }

    /**
     * 部署链。
     *
     * @param reqDeployFiscoChain
     * @return
     */
    public BaseResponse deployWeBASEChain(ReqDeployFiscoChain reqDeployFiscoChain) {
        return httpPost(RestConstant.URI_WEBASE_CHAIN_DEPLOY, reqDeployFiscoChain, BaseResponse.class);
    }


    /**
     * 初始化节点机器（拉镜像、检查端口）
     *
     * @param reqDeployFiscoChain
     * @return
     */
    public List<RespInitHost> nodeHostListInit(ReqDeployFiscoChain reqDeployFiscoChain) {
        BaseResponse baseResponse = httpPost(RestConstant.URI_WEBASE_CHAIN_HOST_LIST_INIT, reqDeployFiscoChain, BaseResponse.class);
        return getResultData(baseResponse, new TypeReference<List<RespInitHost>>() {
        });
    }

    /**
     * 查询主机端口
     *
     * @param reqFrontIpList
     * @return
     */
    public BaseResponse getListFrontByIp(ReqFrontIpList reqFrontIpList){
        return httpPost(RestConstant.URI_WEBASE_FRONT_LIST_BY_IP, reqFrontIpList, BaseResponse.class);
    }


    /**
     * @param chainId
     * @return
     */
    public Integer getDeployProgress(Integer chainId) {
        String uri = String.format(RestConstant.URI_WEBASE_CHAIN_DEPLOY_PROGRESS_FORMAT, chainId);
        BaseResponse baseResponse = httpGet(uri, BaseResponse.class);
        return super.getResultData(baseResponse, Integer.class);
    }

    /**
     * 查询链。
     *
     * @param chainId
     * @return
     */
    public RespChain getChainById(int chainId) {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_CHAIN_GET_BY_ID, chainId), BaseResponse.class);
        RespChain respChain = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()), new TypeReference<RespChain>() {
        });
        return respChain;
    }

    /**
     * 删除链。
     *
     * @param chainId
     * @return
     */
    public BaseResponse deleteByChainId(int chainId) {
        return httpDelete(String.format(RestConstant.URI_WEBASE_CHAIN_DELETE, chainId), null, BaseResponse.class);
    }


    /**
     * @param chainId
     */
    public BaseResponse deleteIfRemoteChainExist(int chainId) {
        log.info("start exec method [deleteIfRemoteChainExist]. chainId:{} ", chainId);
        RespChain respChain = getChainById(chainId);
        log.debug("respChain:{}", JsonUtils.objToString(respChain));
        if (respChain != null) {
            log.info("found remote chain by id:{}, start to delete", chainId);
            return deleteByChainId(chainId);
        }

        log.info("success exec method [deleteIfRemoteChainExist]. chainId:{} ", chainId);
        return BaseResponse.initSuccess();
    }


    /**
     * 创建应用（群组）。
     *
     * @param chainId
     * @param groupId
     * @param orgIdList
     * @param description
     * @return
     */
    public RespGroup createFiscoGroup(int chainId, int groupId, List<Integer> orgIdList, String groupFinalName, String description) {
        ReqGenerateGroup reqGenerateGroup = new ReqGenerateGroup();
        reqGenerateGroup.setGroupName(groupFinalName);
        reqGenerateGroup.setChainId(chainId);
        reqGenerateGroup.setGenerateGroupId(groupId);
        reqGenerateGroup.setTimestamp(BigInteger.valueOf(System.currentTimeMillis()));
        reqGenerateGroup.setOrgIdList(orgIdList);
        reqGenerateGroup.setDescription(description);

        BaseResponse baseResponse = httpPost(RestConstant.URI_WEBASE_GROUP_GENERATE, reqGenerateGroup, BaseResponse.class);
        return super.getResultData(baseResponse, RespGroup.class);
    }

    public static final String SEPARATOR = "SSS";

    /**
     * 获取用户在 WeBASE-Sign 中的 userId。
     *
     * @param appId
     * @param signUserId
     * @return
     */
    public static String getUserIdToSign(String appId, String signUserId) {
        return String.format("%s%s%s", appId, SEPARATOR, signUserId);
    }

    /**
     * @param signUserId
     * @return
     */
    public static String getOriginalUserId(String signUserId) {
        int index = StringUtils.indexOf(signUserId, SEPARATOR);
        if (index > 0) {
            return StringUtils.substring(signUserId, index + SEPARATOR.length());

        }
        return signUserId;
    }

//    /**
//     * 在 WeBASE-Sign 中创建开发者的账号。
//     *
//     * @param developerId
//     * @return
//     */
//    public BaseResponse newUser(int developerId) {
//        return newUser(Constant.WEB_SERVER_APP_ID, String.valueOf(developerId));
//    }

    /**
     * 在 WeBASE-Sign 中添加指定应用的用户账号。
     *
     * @param applicationId
     * @param externalUserId
     * @return
     */
    public BaseResponse newUser(int applicationId, String externalUserId, byte encryptType) {
        return newUser(String.valueOf(applicationId), externalUserId, encryptType);
    }

    /**
     * @param userId
     * @param appId
     * @return
     */
    private BaseResponse newUser(String appId, String userId, byte encryptType) {
        ReqNewUser newUser = new ReqNewUser();
        newUser.setSignUserId(getUserIdToSign(appId, userId));
        newUser.setAppId(appId);
        newUser.setEncryptType(encryptType);
        newUser.setPrivateKey("");
        return newUser(newUser);
    }


    public BaseResponse newUser(ReqNewUser newUser) {
        return httpPost(RestConstant.URI_WEBASE_USER_NEW, newUser, BaseResponse.class);
    }

    public BasePageResponse queryUserByPage(Map<String, Object> param) {
        String uri = WebServerTools.handleUrlParameters(RestConstant.URI_WEBASE_USER_PAGE, param);
        uri = StringUtils.remove(uri, '[');
        uri = StringUtils.remove(uri, ']');
        return httpGet(uri, BasePageResponse.class);
    }

    /**
     * 分页查询 WeBASE-Sign 的用户列表。
     *
     * @param appId
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public Pair<Integer, List<UserInfo>> userList(String appId, int pageNumber, int pageSize) {
        if (StringUtils.isBlank(appId)) {
            throw new WebServerException(ConstantCode.APPLICATION_ID_INVALID);
        }
        pageNumber = pageNumber <= 0 ? 1 : pageNumber;
        pageSize = pageSize <= 0 ? 20 : pageSize;

        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_USER_LIST, appId, pageNumber, pageSize), BaseResponse.class);
        List<UserInfo> list = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<UserInfo>>() {
                });
//        if (CollectionUtils.isNotEmpty(list)) {
//            list.stream().forEach(userInfo -> userInfo.setUserName(getOriginalUserId(userInfo.getUserName())));
//        }
        return Pair.of(baseResponse.getTotalCount(), list);
    }


    /**
     * 保存合约。
     *
     * @param reqContractSave
     * @return
     */
    public RespContract saveContract(ReqContractSave reqContractSave) {
        BaseResponse baseResponse = httpPost(RestConstant.URI_WEBASE_CONTRACT_SAVE, reqContractSave, BaseResponse.class);
        return super.getResultData(baseResponse, RespContract.class);
    }

    /**
     * @param contractId
     * @return
     */
    public RespContract getContractById(Integer contractId) {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_CONTRACT_SAVE, contractId), BaseResponse.class);
        return super.getResultData(baseResponse, RespContract.class);
    }

    /**
     * @param contractId
     * @return
     */
    public RespContract compileByContractId(Integer contractId) {
        BaseResponse baseResponse = httpPost(String.format(RestConstant.URI_WEBASE_COMPILE_BY_CONTRACT_ID, contractId), null, BaseResponse.class);
        return super.getResultData(baseResponse, RespContract.class);
    }


    /**
     * @param restParam
     * @return
     */
    public BasePageResponse pageContractByParam(Map<String, Object> restParam) {
        return httpPost(RestConstant.URI_WEBASE_CONTRACT_PAGE, restParam, BasePageResponse.class);
    }

    /**
     * 分页查询合约
     *
     * @param chainId
     * @param applicationId
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public Pair<Integer, List<RespContract>> selectContract(int chainId, int applicationId, List<Integer> contractIdList, int pageNumber, int pageSize) {
        ReqContractSelect reqContractSelect = new ReqContractSelect();
        if (chainId > 0) {
            reqContractSelect.setChainId(chainId);
            if (applicationId > 0) {
                reqContractSelect.setGroupId(applicationId);
            }
        }
        reqContractSelect.setPageNumber(pageNumber > 0 ? pageNumber : 1);
        reqContractSelect.setPageSize(pageSize > 0 ? pageSize : 20);
        reqContractSelect.setContractIdList(contractIdList);
        BaseResponse baseResponse = httpPost(RestConstant.URI_WEBASE_CONTRACT_SELECT, reqContractSelect, BaseResponse.class);
        List<RespContract> contractList = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespContract>>() {
                });
        return Pair.of(baseResponse.getTotalCount(), contractList);
    }


    /**
     * 查询合约。
     *
     * @param chainId       不能为空
     * @param applicationId application_id，不能为空。
     * @return
     */
    public List<RespContract> selectContract(int chainId, int applicationId) {
        return this.selectContract(chainId, applicationId, null, 0, 100).getValue();
    }

    public List<RespContract> selectContract(List<Integer> contractIdList, int pageNumber, int pageSize) {
        return this.selectContract(0, 0, contractIdList, pageNumber, pageSize).getValue();
    }

    /**
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public Pair<Integer, List<RespContract>> selectAllContractByPage(int pageNumber, int pageSize) {
        return this.selectContract(0, 0, null, pageNumber, pageSize);
    }


    /**
     * 根据合约在 WeBASE-Chain-Manager 中的 id 部署合约。
     *
     * @param contractId
     * @param signUserId
     * @param constructorParams
     * @return
     */
    public RspContractDeployDTO deployContract(int contractId, String signUserId, List<Object> constructorParams) {
        if (constructorParams == null)
            constructorParams = new ArrayList<>();


        // param
        Map<String, Object> interFaceParam = new HashedMap();
        interFaceParam.put("signUserId", signUserId);
        interFaceParam.put("contractId", contractId);
        interFaceParam.put("constructorParams", constructorParams);

        BaseResponse baseResponse = httpPost(RestConstant.URI_WEBASE_CONTRACT_DEPLOY, interFaceParam, BaseResponse.class);
        return super.getResultData(baseResponse, RspContractDeployDTO.class);
    }


    /**
     * 删除合约.
     *
     * @param chainId
     * @param groupId
     * @param contractId
     * @return
     */
    public BaseResponse deleteContract(int chainId, int groupId, int contractId) {
        return httpDelete(String.format(RestConstant.URI_WEBASE_CONTRACT_DELETE, chainId, groupId, contractId), null, BaseResponse.class);
    }


    /**
     * 发送交易.
     *
     * @param param
     * @return
     */
    public BaseResponse sendTransaction(ReqFiscoSendTransDto param) {
        return httpPost(RestConstant.URI_WEBASE_SEND_TRANSACTION, param, BaseResponse.class);
    }


    /**
     * @return
     */
    public BaseResponse queryImageTypeList() {
        return httpGet(URI_WEBASE_CHAIN_IMAGE_TYPE_LIST, BaseResponse.class);
    }


    /**
     * 查询所有的节点前置列表。
     *
     * @return
     */
    public List<RespFront> selectFrontList() {
        return selectFrontListByChainId(0, 0);
    }

    /**
     * 查询链的节点前置列表。
     *
     * @return
     */
    public List<RespFront> selectFrontListByChainId(int chainId, int groupId) {
        Map<String, String> paramMap = new HashedMap<>();
        if (chainId > 0) {
            paramMap.put("chainId", String.valueOf(chainId));

            if (groupId > 1) {
                paramMap.put("groupId", String.valueOf(groupId));
            } else {
                paramMap.put("groupId", String.valueOf(1));
            }
        }


        BaseResponse baseResponse = httpGet(buildUri(RestConstant.URI_WEBASE_FRONT_LIST, paramMap), BaseResponse.class);

        return JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespFront>>() {
                });
    }

    public List<RespFront> selectFrontListByChainAndOrgId(int chainId, int orgId) {
        Map<String, String> paramMap = new HashedMap<>();
        if (chainId > 0) {
            paramMap.put("chainId", String.valueOf(chainId));
        }
        if (orgId > 0) {
            paramMap.put("agencyId", String.valueOf(orgId));
        }
        BaseResponse baseResponse = httpGet(buildUri(RestConstant.URI_WEBASE_FRONT_LIST, paramMap), BaseResponse.class);

        return JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespFront>>() {
                });
    }

    public BaseResponse addNode(ReqAddNewNode2ChainVO reqAddNewNode2ChainVO, List<ReqDeployFiscoChain.DeployHost> deployHostList) {
        Map<String, Object> paramMap = new HashedMap<>();
        paramMap.put("chainId", reqAddNewNode2ChainVO.getChainId());
        paramMap.put("chainName", reqAddNewNode2ChainVO.getChainId());
        paramMap.put("agencyName", reqAddNewNode2ChainVO.getOrgId());
        paramMap.put("deployHostList", deployHostList);
        paramMap.put("description", reqAddNewNode2ChainVO.getDescription());
        return httpPost(RestConstant.URI_WEBASE_CHAIN_ADD_NODE, paramMap, BaseResponse.class);
    }

    /**
     * 查询群组的节点类型（共识，观察，游离）。
     *
     * @param chainId
     * @param groupId
     * @return Map<K, V>  : K, nodeId; V, nodeType("remove","sealer","observer");
     */
    public Map<String, String> selectNodeConsensusTypeMap(int chainId, int groupId) {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_GROUP_NODE_LIST, chainId, groupId), BaseResponse.class);

        List<RespNodeConsensus> nodeConsensusList = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespNodeConsensus>>() {
                });

        return Optional.of(nodeConsensusList).orElse(Collections.emptyList()).stream()
                .collect(Collectors.toMap(RespNodeConsensus::getNodeId, RespNodeConsensus::getNodeType));
    }

    /**
     * get all node in syatem
     */
    public BaseResponse getAllNodeSystem() {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_NODE_ALL), BaseResponse.class);
        List<RespNode> nodeList = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespNode>>() {
                });
        int actvCount = 0;
        for (RespNode respNode : nodeList) {
            if (respNode.getNodeActive() == 1) {
                actvCount++;
            }
        }
        JSONObject newJsonObject = new JSONObject();
        newJsonObject.set("totalCount", nodeList.size());
        newJsonObject.set("runningCount", actvCount);
        baseResponse.setData(newJsonObject);
        return baseResponse;
    }


    /**
     * 查询群组的节点类型（共识，观察，游离）。
     *
     * @param chainId
     * @param groupId
     * @return Map<K, V>  : K, nodeId; V, nodeType("remove","sealer","observer");
     */
    public Map<String, RespNode> selectNodeInfoList(int chainId, int groupId) {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_NODE_LIST, chainId, groupId), BaseResponse.class);

        List<RespNode> nodeList = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RespNode>>() {
                });

        return Optional.of(nodeList).orElse(Collections.emptyList()).stream()
                .collect(Collectors.toMap(RespNode::getNodeId, Function.identity()));
    }


    /**
     * @param chainId
     * @param groupId
     * @param nodeTypes
     * @return
     */
    public List<RspSimpleNodeInfoVo> selectNodeIdList(int chainId, int groupId, int orgId, String nodeTypes) {
        BaseResponse baseResponse = httpGet(String.format(RestConstant.URI_WEBASE_NODE_ID_LIST, chainId, groupId, orgId, nodeTypes), BaseResponse.class);

        List<RspSimpleNodeInfoVo> nodeIdList = JsonUtils.stringToObj(JsonUtils.objToString(baseResponse.getData()),
                new TypeReference<List<RspSimpleNodeInfoVo>>() {
                });

        return nodeIdList;
    }


    /**
     * @param chainId
     * @param groupId
     * @param nodeIds
     * @return
     */
    public void addSealerAsync(int chainId, int groupId, Set<String> nodeIds) {
        // param
        Map<String, Object> restParam = new HashedMap();
        restParam.put("chainId", chainId);
        restParam.put("groupId", groupId);
        restParam.put("nodeIdList", nodeIds);
        BaseResponse restResponse = httpPost(RestConstant.URI_WEBASE_GROUP_ADD_SEALER_ASYNC, restParam, BaseResponse.class);
        super.checkRestResult(restResponse);
    }

    /**
     * @param chainId
     * @param groupId
     * @param nodeIds
     * @return
     */
    public BaseResponse addSealerAsyncReturnValue(int chainId, int groupId, Set<String> nodeIds) {
        // param
        Map<String, Object> restParam = new HashedMap();
        restParam.put("chainId", chainId);
        restParam.put("groupId", groupId);
        restParam.put("nodeIdList", nodeIds);
        BaseResponse restResponse = httpPost(RestConstant.URI_WEBASE_GROUP_ADD_SEALER_ASYNC, restParam, BaseResponse.class);
        return restResponse;
    }

    /**
     * @param chainId
     * @param groupId
     * @param nodeIds
     */
    public void removeNodeFromGroup(int chainId, int groupId, Set<String> nodeIds) {
        // param
        Map<String, Object> restParam = new HashedMap();
        restParam.put("chainId", chainId);
        restParam.put("groupId", groupId);
        restParam.put("nodeType", "remove");
        restParam.put("nodeIdList", nodeIds);

        BaseResponse restResponse = httpPost(RestConstant.URI_WEBASE_GROUP_SET_CONSENSUS_STATUS, restParam, BaseResponse.class);
        super.checkRestResult(restResponse);
    }


    @Override
    public <T> T httpGet(String uri, Class<T> clazz) {
        return super.restTemplateExchange(serverName, RestConstant.FISCO_CHAIN_MANAGER_URL_FORMAT, uri, HttpMethod.GET, null, clazz);
    }

    @Override
    public <T> T httpPost(String uri, Object param, Class<T> clazz) {
        return super.restTemplateExchange(serverName, RestConstant.FISCO_CHAIN_MANAGER_URL_FORMAT, uri, HttpMethod.POST, param, clazz);
    }

    @Override
    public <T> T httpPut(String uri, Object param, Class<T> clazz) {
        return super.restTemplateExchange(serverName, RestConstant.FISCO_CHAIN_MANAGER_URL_FORMAT, uri, HttpMethod.PUT, param, clazz);
    }

    @Override
    public <T> T httpDelete(String uri, Object param, Class<T> clazz) {
        return super.restTemplateExchange(serverName, RestConstant.FISCO_CHAIN_MANAGER_URL_FORMAT, uri, HttpMethod.DELETE, param, clazz);
    }

    /**
     * @param baseUrl
     * @param paramMap
     * @return
     */
    private static String buildUri(String baseUrl, Map<String, String> paramMap) {
        List<String> paramList = new ArrayList<>();

        Optional.of(paramMap).ifPresent((map) -> {
            paramList.addAll(map.entrySet().stream().map(entry -> String.format("%s=%s", entry.getKey(), entry.getValue()))
                    .collect(Collectors.toList()));
        });
        return String.format("%s?%s", RestConstant.URI_WEBASE_FRONT_LIST, StringUtils.join(paramList, "&"));
    }


}
